Skip to content

Add Java Durable Task SDK sample for Work Item Filtering#276

Open
bachuv wants to merge 9 commits intoAzure-Samples:mainfrom
bachuv:vabachu/work-item-filters-java
Open

Add Java Durable Task SDK sample for Work Item Filtering#276
bachuv wants to merge 9 commits intoAzure-Samples:mainfrom
bachuv:vabachu/work-item-filters-java

Conversation

@bachuv
Copy link
Copy Markdown
Contributor

@bachuv bachuv commented Apr 24, 2026

Purpose

Adds a Java port of the Work Item Filtering split activities scenario sample (samples/scenarios/WorkItemFilteringSplitActivitiesJava), using the standalone Durable Task SDK (not Durable Functions). This demonstrates the new useWorkItemFilters() API from durabletask-java v1.9.0 to route orchestration and activity work items to dedicated workers.

Note: Work item filtering for Java is currently supported only in the standalone Durable Task SDK (durabletask-client / durabletask-azuremanaged). It is not yet available in the Durable Functions Java extension.

What's included

  • 4 standalone Java services (Gradle multi-project build):
    • Orchestrator Worker — registers only OrderProcessingOrchestration
    • Validator Worker — registers only ValidateOrder activity
    • Shipper Worker — registers only ShipOrder activity
    • Client — schedules orchestration batches in a loop and prints results
  • Shared ConnectionHelper — builds DTS connection strings from environment variables (emulator, Managed Identity, DefaultAzure)
  • Dockerfiles for each service (Eclipse Temurin 21)
  • Bicep infrastructure and azure.yaml for one-command azd up deployment to Azure Container Apps with KEDA scaling
  • README with architecture diagram, step-by-step local run instructions, expected output, and Azure deployment guide

How it works

Each worker calls .useWorkItemFilters() on the DurableTaskGrpcWorkerBuilder to auto-generate filters from registered tasks. DTS routes work items only to workers with matching filters — no cross-processing occurs.

Tested locally

Verified end-to-end against the DTS emulator (mcr.microsoft.com/dts/dts-emulator:latest):

  • All 3 orchestrations per batch complete successfully
  • Orchestrator Worker handles only orchestration replays
  • Validator Worker handles only ValidateOrder activities
  • Shipper Worker handles only ShipOrder activities

Related

Does this introduce a breaking change?

  • Yes
  • No

Pull Request Type

What kind of change does this Pull Request introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Documentation content changes
  • Other... Please describe:

How to Test

  • Get the code
git clone https://github.com/Azure-Samples/Durable-Task-Scheduler.git
cd Durable-Task-Scheduler
git checkout vabachu/work-item-filters-java
  • Start the DTS emulator
docker pull mcr.microsoft.com/dts/dts-emulator:latest
docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
  • Build the sample
cd samples/scenarios/WorkItemFilteringSplitActivitiesJava
./gradlew build
  • Start the three workers (each in a separate terminal)
./gradlew :orchestrator-worker:run
./gradlew :validator-worker:run
./gradlew :shipper-worker:run
  • Run the client (in a fourth terminal)
./gradlew :client:run

What to Check

Verify that the following are valid:

  • Client schedules 3 orchestrations per batch and all complete successfully
  • Orchestrator Worker logs only orchestration processing (no activity execution)
  • Validator Worker logs only ValidateOrder activity work items
  • Shipper Worker logs only ShipOrder activity work items
  • Stopping Shipper Worker causes ShipOrder work items to remain pending (not routed to other workers)
  • Restarting Shipper Worker causes pending ShipOrder work items to complete immediately

Other Information

  • Requires Java 21+ and Docker
  • Uses durabletask-client:1.9.0 and durabletask-azuremanaged:1.9.0 from Maven local (not yet published to Maven Central)
  • This is a standalone Durable Task SDK sample — work item filtering is not yet available in the Durable Functions Java extension

@bachuv bachuv marked this pull request as ready for review April 27, 2026 23:12
@torosent
Copy link
Copy Markdown
Collaborator

Tested this PR locally end-to-end against the DTS emulator (built durabletask-{client,azuremanaged}:1.9.0 from the merged microsoft/durabletask-java#275 — note that 1.9.0 is now on Maven Central as of 2026-04-24, so the "draft until SDK release" gate is cleared).

All functional checklist items pass: orchestrations complete 3/3 across batches, each worker only processes its registered work item types (Orchestrator: 30 dispatches / 0 activities; Validator: 36 ValidateOrder / 0 ShipOrder / 0 orchestrations; Shipper: 24 ShipOrder / 0 ValidateOrder / 0 orchestrations), the strict-routing experiment works as documented (stop Shipper → ShipOrder backlogs, restart → backlog drains immediately), and az bicep build compiles all 11 templates cleanly.

Found four issues worth addressing before merge:

1. gradlew is committed without the execute bit

git ls-tree HEAD samples/scenarios/WorkItemFilteringSplitActivitiesJava/gradlew reports mode 100644, but every other Java sample in this repo has it as 100755:

100755 .../async-http-api/gradlew
100755 .../eternal-orchestrations/gradlew
100755 .../fan-out-fan-in/gradlew
...
100644 .../WorkItemFilteringSplitActivitiesJava/gradlew   ← this PR

Without it, the README's ./gradlew build step fails with Permission denied on macOS/Linux. Fix:

git update-index --chmod=+x samples/scenarios/WorkItemFilteringSplitActivitiesJava/gradlew

2. README documents activity log lines that the Java SDK can't produce

The "Expected Output" section shows:

[Validator] Activity | Name=ValidateOrder | InstanceId=abc123 | Validating order 'ORD-B001-001'...
[Shipper]   Activity | Name=ShipOrder    | InstanceId=abc123 | Shipping order 'ORD-B001-001'...

But TaskActivityContext in durabletask-client only exposes getName() and getInput(...) — there is no getInstanceId(). The actual code in ValidatorWorker.java / ShipperWorker.java correctly omits InstanceId, so what the runtime actually emits is:

[Validator] Activity | Name=ValidateOrder | Validating order 'ORD-B001-001'...
[Shipper]   Activity | Name=ShipOrder | Shipping order 'ORD-B001-001'...

(The .NET equivalent has context.InstanceId available, which is presumably where the example was copied from.) Either drop the InstanceId=... segments from the README's example output, or add getInstanceId() to the Java SDK and then log it.

3. Prerequisite Java version is inconsistent

  • README.md line 55: [Java 21](https://adoptium.net/) (or later)
  • build.gradle lines 13–14: sourceCompatibility = JavaVersion.VERSION_11 / targetCompatibility = JavaVersion.VERSION_11
  • PR description: "Requires Java 11+ and Docker"

The Dockerfiles use Temurin 21 for image builds, but the local-build target is 11. Either bump the source/target to 21 in build.gradle or change the README prereq to "Java 11+" to match the PR description.

4. CI doesn't validate this sample builds

.github/workflows/build-samples.yml lists every existing Java sample under samples/durable-task-sdks/java/ (function-chaining, fan-out-fan-in, sub-orchestrations, human-interaction, monitoring, eternal-orchestrations, async-http-api, opentelemetry-tracing) but has no step for samples/scenarios/WorkItemFilteringSplitActivitiesJava. That's why the "Java Samples" check is green even with the missing +x bit on gradlew. Suggested addition:

- name: Build Scenario - WorkItemFilteringSplitActivitiesJava
  run: cd samples/scenarios/WorkItemFilteringSplitActivitiesJava && chmod +x gradlew && ./gradlew build

(chmod +x gradlew becomes unnecessary once #1 is fixed.)

Copilot AI review requested due to automatic review settings May 4, 2026 22:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a standalone Java scenario sample demonstrating Durable Task Scheduler work item filtering (useWorkItemFilters()), including local/Docker execution and Azure Container Apps deployment assets.

Changes:

  • Introduces a new Gradle multi-project Java scenario sample (client + 3 dedicated workers) that uses DTS work item filters for strict routing.
  • Adds Dockerfiles/logging config and shared connection-string helper for running the sample locally or in containers.
  • Adds Bicep + azure.yaml to deploy the sample to Azure Container Apps with KEDA scaling, and updates CI to build the new sample.

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
samples/scenarios/WorkItemFilteringSplitActivitiesJava/README.md Documents the scenario, architecture, run steps, and Azure deployment.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/azure.yaml Defines 4 containerapp services for azd up.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/build.gradle Root Gradle configuration and shared dependencies for all subprojects.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/settings.gradle Declares the multi-project build structure.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/.gitignore Ignores Gradle/IDE/build outputs for the sample.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/gradlew Adds Gradle wrapper script for POSIX.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/gradlew.bat Adds Gradle wrapper script for Windows.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/gradle/wrapper/gradle-wrapper.properties Pins Gradle wrapper distribution version.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/shared/src/main/java/io/durabletask/samples/ConnectionHelper.java Provides env-var based connection-string construction for emulator/Azure.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/shared/build.gradle Declares the shared module (currently only a comment).
samples/scenarios/WorkItemFilteringSplitActivitiesJava/client/src/main/java/io/durabletask/samples/Client.java Adds client that schedules orchestration batches and waits for completion.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/client/src/main/resources/logback.xml Adds console logging config for the client.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/client/build.gradle Configures the client app entry point and shared dependency.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/client/Dockerfile Builds/runs the client as a container image.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/orchestrator-worker/src/main/java/io/durabletask/samples/OrchestratorWorker.java Adds orchestration-only worker demonstrating filter-based routing.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/orchestrator-worker/src/main/resources/logback.xml Adds console logging config for orchestrator worker.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/orchestrator-worker/build.gradle Configures orchestrator worker app entry point and shared dependency.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/orchestrator-worker/Dockerfile Builds/runs orchestrator worker as a container image.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/validator-worker/src/main/java/io/durabletask/samples/ValidatorWorker.java Adds ValidateOrder-only activity worker with auto-generated filters.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/validator-worker/src/main/resources/logback.xml Adds console logging config for validator worker.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/validator-worker/build.gradle Configures validator worker app entry point and shared dependency.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/validator-worker/Dockerfile Builds/runs validator worker as a container image.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/shipper-worker/src/main/java/io/durabletask/samples/ShipperWorker.java Adds ShipOrder-only activity worker with auto-generated filters.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/shipper-worker/src/main/resources/logback.xml Adds console logging config for shipper worker.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/shipper-worker/build.gradle Configures shipper worker app entry point and shared dependency.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/shipper-worker/Dockerfile Builds/runs shipper worker as a container image.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/main.bicep Provisions RG, identity, DTS, and 4 Container Apps in ACA.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/main.parameters.json Parameter file for deployment values via azd.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/abbreviations.json Resource name abbreviations for consistent naming.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/app/app.bicep Deploys each service container app with KEDA custom scaler parameters.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/app/dts.bicep Provisions DTS scheduler + task hub.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/app/user-assigned-identity.bicep Creates shared user-assigned managed identity.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/core/host/container-app.bicep Container App template including identity/registry/KEDA settings.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/core/host/container-apps.bicep Provisions ACA environment + ACR.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/core/host/container-apps-environment.bicep Creates ACA managed environment (optional VNet integration).
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/core/host/container-registry.bicep Creates ACR and optional diagnostics.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/core/networking/vnet.bicep Creates VNet + subnets for ACA integration.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/core/security/role.bicep Creates role assignments for identity/user access to DTS.
samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/core/security/registry-access.bicep Assigns ACR Pull role assignment for the managed identity.
.github/workflows/build-samples.yml Updates CI to build additional Java samples including this scenario.
Comments suppressed due to low confidence (4)

samples/scenarios/WorkItemFilteringSplitActivitiesJava/shared/src/main/java/io/durabletask/samples/ConnectionHelper.java:1

  • ConnectionHelper is placed in the shared module but is package-private, as is getConnectionString(). While package access can work across JARs if they share the same package name and classloader, this is fragile and surprising for a shared utility. Making the class and method public would make the intended cross-module usage explicit and reduce the chance of access issues in different packaging/runtime setups.
    samples/scenarios/WorkItemFilteringSplitActivitiesJava/shared/src/main/java/io/durabletask/samples/ConnectionHelper.java:1
  • ConnectionHelper is placed in the shared module but is package-private, as is getConnectionString(). While package access can work across JARs if they share the same package name and classloader, this is fragile and surprising for a shared utility. Making the class and method public would make the intended cross-module usage explicit and reduce the chance of access issues in different packaging/runtime setups.
    samples/scenarios/WorkItemFilteringSplitActivitiesJava/shared/src/main/java/io/durabletask/samples/ConnectionHelper.java:1
  • Local-emulator detection is overly strict: it only matches exactly http://localhost:8080. If a user sets ENDPOINT to http://127.0.0.1:8080, includes a trailing slash, or uses a different localhost port mapping, the code will incorrectly select Azure authentication. Parsing endpoint as a URI and checking for localhost/loopback (and/or allowing a configurable emulator-auth override) would make local runs much more reliable.
    samples/scenarios/WorkItemFilteringSplitActivitiesJava/validator-worker/src/main/java/io/durabletask/samples/ValidatorWorker.java:1
  • Logging the full connection string can leak sensitive data if DURABLE_TASK_CONNECTION_STRING ever includes credentials (or if future auth modes add secrets). Consider logging only non-sensitive parts (e.g., endpoint/task hub) or redacting known secret fields before logging.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread samples/scenarios/WorkItemFilteringSplitActivitiesJava/build.gradle
Comment thread .github/workflows/build-samples.yml
Comment thread samples/scenarios/WorkItemFilteringSplitActivitiesJava/build.gradle Outdated
Comment thread samples/scenarios/WorkItemFilteringSplitActivitiesJava/build.gradle
Comment thread samples/scenarios/WorkItemFilteringSplitActivitiesJava/infra/main.bicep Outdated
Copilot AI review requested due to automatic review settings May 5, 2026 00:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 42 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings May 6, 2026 19:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 43 changed files in this pull request and generated 3 comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants